package org.fujene.communicate.blockwise;

import java.io.IOException;
import java.net.UnknownHostException;
import java.util.TreeSet;
import java.util.logging.Logger;

import org.fujene.communicate.node.Node;
import org.fujene.constants.Constants;
import org.fujene.structure.exception.DataMatchingException;
import org.msgpack.MessagePackObject;
import org.msgpack.object.ArrayType;
import org.msgpack.object.IntegerType;
import org.msgpack.object.NilType;
import org.msgpack.object.RawType;
import org.msgpack.rpc.Request;

public class BlockwisePeerHandler{
	private Logger logger;
	private BlockwisePeerSystem parentSystem;

	public BlockwisePeerHandler(Logger logger, BlockwisePeerSystem parentSystem){
		this.logger = logger;
		this.parentSystem = parentSystem;
	}

	/**
	 * ノードチェーンを送信します。
	 * 
	 * @param req
	 *            ... なし
	 */
	public void getNodeList(Request req){
		MessagePackObject[] output = null;
		int count = 0;

		TreeSet<Node> currentInfos = new TreeSet<Node>(parentSystem.getNode());
		output = new MessagePackObject[2 * currentInfos.size()];

		for(Node eachPeer: currentInfos){
			output[count++] = RawType.create(eachPeer.getAddr());
			output[count++] = IntegerType.create(eachPeer.getPort());
		}

		logger.finest("getNodeList - called.");

		req.sendResult(ArrayType.create(output));
	}

	/**
	 * ブロックの情報を取得します。
	 * 
	 * @param req
	 *            ... 引数なし
	 */
	public void blockList(Request req){
		int[] blockNums = parentSystem.getBlockList();
		MessagePackObject[] objs = new MessagePackObject[blockNums.length * 2];

		for(int i = 0; i < blockNums.length; i++){
			objs[2 * i] = IntegerType.create(blockNums[i] / 100);
			objs[2 * i + 1] = IntegerType.create(blockNums[i] % 100);
		}

		req.sendResult(ArrayType.create(objs));
	}

	/**
	 * 検索エンジンの情報各種を送信します。
	 * 
	 * @param req
	 *            ... なし
	 */
	public void getInfos(Request req){
		MessagePackObject output = parentSystem.getInfo();

		logger.finest("getInfos - called.");

		req.sendResult(output);
	}

	/**
	 * ノードを登録します。
	 * 
	 * @param req
	 *            ... 0: (RawType/String)IPアドレス, 1: (IntegerType/int)本文チェーンハッシュ,
	 *            2: (IntegerType/int)辞書チェーンハッシュ
	 */
	public void register(Request req){
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		if (objs.length < 2 || (!objs[0].isRawType() && !objs[1].isIntegerType())){
			req.sendError(RawType.create("ERROR(register): Argument is invalid"));
			logger.severe("ERROR(register): Argument is invalid");
			return;
		}

		try{
			parentSystem.addReplication(Node.create(objs[0].asString(), objs[1].asInt()));
		}catch(UnknownHostException e){
		}

		logger.finest("register - called.");

		req.sendResult(NilType.create());
	}

	/**
	 * ノードを削除します。
	 * 
	 * @param req
	 *            ... 0: (IntegerType/long)削除ハッシュ
	 */
	public void delete(Request req){
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		if (objs.length < 1 || (!objs[0].isIntegerType())){
			req.sendError(RawType.create("ERROR(delete): Argument is invalid"));
			logger.severe("ERROR(delete): Argument is invalid");
			return;
		}

		try{
			parentSystem.deleteReplication(Node.createLocal(null, objs[0].asInt()));
		}
		catch(DataMatchingException e){}
		catch(IOException e){}

		logger.finest("delete - called.");

		req.sendResult(NilType.create());
	}

	/**
	 * IndexMemory の各関数に呼び出しをかけます。
	 * 
	 * @param req
	 *            ...0:(RawType/String)関数名, 1:(RawType/byte[])引数
	 */
	public void callGetFunc(Request req){
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		if (objs.length < 2 || (!objs[0].isRawType() && !objs[1].isRawType())){
			req.sendError(RawType.create("ERROR(callFunc): Argument is invalid"));
			logger.severe("ERROR(callFunc): Argument is invalid");
			return;
		}

		byte[] args = objs[1].asByteArray();

		byte[] returns = parentSystem.invokeGetFunc(objs[0].asString(), args);

		logger.finest("callGetFunc - " + objs[0].asString() + " - called.");

		try{
			req.sendResult(RawType.create(returns));
		}
		catch(Exception e){
			logger.info("Connection closed.");
		}
	}

	/**
	 * IndexMemory の各関数に呼び出しをかけます。
	 * 
	 * @param req
	 *            ...0:(RawType/String)関数名, 1:(RawType/byte[])引数
	 */
	public void callSetFunc(Request req){
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		if (objs.length < 2 || (!objs[0].isRawType() && !objs[1].isRawType())){
			req.sendError(RawType.create("ERROR(callFunc): Argument is invalid"));
			logger.severe("ERROR(callFunc): Argument is invalid");
			return;
		}

		byte[] args = objs[1].asByteArray();

		byte[] returns = parentSystem.invokeSetFunc(objs[0].asString(), args);

		logger.finest("callSetFunc - " + objs[0].asString() + " - called.");

		try{
			req.sendResult(RawType.create(returns));
		}
		catch(Exception e){
			logger.info("Connection closed.");
		}
	}

	/**
	 * メモリブロックを取得します。
	 * 
	 * @param req
	 *            ... 0:(IntegerType/int)ブロック番号 1:(IntegerType/int)ブロック種類
	 *            2:(IntegerType/int)本文種類
	 */
	public void getBlock(Request req){
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		if (objs.length < 1 || (!objs[0].isIntegerType())){
			req.sendError(RawType.create("ERROR(getBlock): Argument is invalid"));
			logger.severe("ERROR(getBlock): Argument is invalid");
			return;
		}
		int blockIndex = objs[0].asInt();

		MessagePackObject[] postobjs = new MessagePackObject[3];

		postobjs[0] = RawType.create(parentSystem.getBlock(blockIndex));
		int blockType = parentSystem.getBlockType(blockIndex);
		postobjs[1] = IntegerType.create(blockType);
		if (blockType == Constants.MEMORY_TYPE_CONTENTS || blockType == Constants.MEMORY_TYPE_APPENDIX)
			postobjs[2] = IntegerType.create(parentSystem.getContentType(blockIndex));
		else postobjs[2] = IntegerType.create(0);

		logger.finest("getBlock - called.");

		req.sendResult(ArrayType.create(postobjs));
	}

	/**
	 * メモリブロックを作成させます。
	 * 
	 * @param req
	 *            ... 0:(IntegerType/int)ブロック番号 1:(IntegerType/int)ブロック種類,
	 *            2:(IntegerType/int)本文種類 3:(BooleanType/boolean)実際にメモリ確保するかどうか
	 */
	public void setBlockInfo(Request req){
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		if (objs.length < 4
				|| (!objs[0].isIntegerType() && !objs[1].isIntegerType() && !objs[2].isIntegerType() && !objs[3]
						.isBooleanType())){
			req.sendError(RawType.create("ERROR(setBlockInfo): Argument is invalid"));
			logger.severe("ERROR(setBlockInfo): Argument is invalid");
			return;
		}

		parentSystem.pushBlock(objs[0].asInt(), objs[1].asInt(), objs[2].asInt(), objs[3].asBoolean());

		logger.finest("setBlockInfo - called.");

		req.sendResult(NilType.create());
	}

	/**
	 * 生存しているかどうか返します。
	 * 
	 * @param req
	 *            ... 引数なし
	 */
	public void isLive(Request req){
		logger.finest("isLive - called.");

		req.sendResult(RawType.create("Live"));
	}
}